package gui.batches;

import static org.junit.Assert.*;
import gui.inventory.ProductContainerData;
import gui.item.ItemData;
import gui.product.ProductData;

import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;

import org.junit.Before;
import org.junit.Test;

import persistence.DataPersistor;

import common.util.DateUtils;

import core.Container;
import core.ContainerFacade;
import core.Item;
import core.ItemFacade;
import core.Product;
import core.ProductFacade;
import core.exception.IllegalAddException;
import core.exception.ModelException;


// Note: the initial storage unit tree setup for this test is:
//
// RootStorageUnit:
//     StorageUnit1 (Has Product1, and 3 items of Product1)
//         ProductGroup1 (Has Product2, but no items -- item was added 
//						  and then removed)
// 
// and _target (ProductContainerData) is set to StorageUnit1
public class AddItemBatchControllerTest implements IAddItemBatchView {
	private AddItemBatchController _controller;
	
	private String _expectedErrorMessage;
	
	private ProductData[] _currentProducts;
	private ProductData _selectedProduct;
	
	private ItemData[] _currentItems;
	private ItemData _selectedItem;
	
	private Boolean _shouldCallClose;
	private Boolean _didCallClose;
	
	private Boolean _shouldCallGiveBarcodeFocus;
	private Boolean _didCallGiveBarcodeFocus;
	
	private Boolean _shouldCallShowProductView;
	private Boolean _didCallShowProductView;
	private Boolean _shouldAddNewProduct;
	
	private Date _currentEntryDate;
	private String _currentCount;
	private String _currentBarcode;
	private Boolean _useBarcodeScanner;
	private Boolean _addItemEnabled;
	
	private Boolean _notifyOnSelection;
	
	private ProductContainerData _target;


	@Before
	public void setUp() throws Exception {
		DataPersistor.instance().init("-serial");
		this._expectedErrorMessage = null;
		
		this._currentProducts = null;
		this._selectedProduct = null;
		
		this._currentItems = null;
		this._selectedItem = null;
		
		this._shouldCallClose = false;
		this._didCallClose = false;
		
		// Constructing a controller should call this
		// and a controller is constructed in setup.
		this._shouldCallGiveBarcodeFocus = true;
		this._didCallGiveBarcodeFocus = false;
		
		this._currentBarcode = "";
		this._currentCount = "";
		this._currentEntryDate = null;
		this._useBarcodeScanner = false;
		this._addItemEnabled = false;
		
		
		this._notifyOnSelection = false;
		
		this._shouldCallShowProductView = false;
		this._didCallShowProductView = false;
		this._shouldAddNewProduct = false;
		
		ContainerFacade.clearAllData();
		ProductFacade.clearAllData();
		ItemFacade.clearAllData();
		
		Container su = ContainerFacade.instance().addStorageUnit("StorageUnit1");
		Container pg = ContainerFacade.instance().addProductGroup(su, 
				"ProductGroup1", "0", "count");
		
		ProductFacade.instance().addProduct(new Product("11", "Product1", "1", "count", 0, 0));
		ProductFacade.instance().addProduct(new Product("22", "Product2", "1", "count", 0, 0));
		
		Product product1 = ProductFacade.instance().getProduct("11");
		Product product2 = ProductFacade.instance().getProduct("22");
		
		ItemFacade.instance().addItem(product1, su);
		ItemFacade.instance().addItem(product1, su);
		ItemFacade.instance().addItem(product1, su);
		
		Item i = ItemFacade.instance().addItem(product2, pg);
		ItemFacade.instance().removeItem(i);
		
		_target = new ProductContainerData(su);
		_controller = new AddItemBatchController(this,_target);
	}

	@Test
	public void testAddItemBatchController() {
		assertEquals("Functional spec pg 17 second paragraph under adding items: " +
				"The entry date field is automatically initialized to the current date", 
				DateUtils.currentDate(),_currentEntryDate);
		assertEquals("Functional spec pg 17 second paragraph under adding items:"+
				"The count field is automatically intialized to 1","1",_currentCount);
		assertTrue("Functional spec pg 17 second paragraph under adding items:" + 
				"The keyboard focus is automatically given to the Product Barcode Field",
				_didCallGiveBarcodeFocus);
	}

	@Test
	public void testEntryDateChanged() throws ParseException {
		// initially give a valid barcode so we're just testing the
		// ability to do valid dates.
		this.setBarcode("11");
		
		assertTrue("With a valid barcode, should initially be able to add item",_addItemEnabled);
	
		Date invalid = DateUtils.parseDate("12/31/1999");
		Date invalid2 = DateUtils.parseDate("01/01/2222");
		Date valid = DateUtils.parseDate("01/31/2012");
		Date valid2 = DateUtils.parseDate("03/03/2010");
		
		setEntryDate(invalid);
		assertFalse("Invalid date, should not be able to click okay",_addItemEnabled);
		
		setEntryDate(valid);
		assertTrue("Valid date, should be able to click okay",_addItemEnabled);
		
		setEntryDate(invalid2);
		assertFalse("Invalid date, should not be able to click okay",_addItemEnabled);
		
		setEntryDate(valid2);
		assertTrue("Valid date, should be able to click okay",_addItemEnabled);
		
	
	}

	@Test
	public void testCountChanged() {
		// note: this is only testing for the manual mode 
		// (where addItemEnabled and addItemDisabled matter)
		this.setBarcode("11");
		
		assertTrue("With a valid barcode, should initially be able to add item",_addItemEnabled);

		setCount("-");
		assertFalse("Not positive",_addItemEnabled);
		
		setCount("4");
		assertTrue(_addItemEnabled);
		
		setCount("-1");
		assertFalse(_addItemEnabled);
		
		setCount("3");
		assertTrue(_addItemEnabled);
		
		setCount("-1.0");
		assertFalse(_addItemEnabled);
		
		setCount("50");
		assertTrue(_addItemEnabled);
		
		setCount("1.0");
		assertFalse(_addItemEnabled);
		
		setCount("22");
		assertTrue(_addItemEnabled);
		
		setCount("1.3");
		assertFalse(_addItemEnabled);
		
		setCount("1000000");
		assertTrue(_addItemEnabled);
		
		setCount("abc");
		assertFalse(_addItemEnabled);
		
		setCount("5");
		assertTrue(_addItemEnabled);
		
		setCount("");
		assertFalse(_addItemEnabled);
		
	}

	@Test
	public void testBarcodeChanged() {
		assertFalse("Initially, barcode is empty", _addItemEnabled);
		
		setBarcode("99999");
		assertTrue(_addItemEnabled);
		
		setBarcode("");
		assertFalse(_addItemEnabled);
		
		setBarcode("%9g091kdskh;eriowkd");
		assertTrue("Any nonempty product barcode should be valid", _addItemEnabled);
	}

	@Test
	public void testUseScannerChanged() {
		// initially give a valid barcode so we're just testing the
		// ability to do valid dates.
		this.setBarcode("11");
		assertTrue(_addItemEnabled);
		
		this.setUseScanner(true);
		assertFalse(_addItemEnabled);
	}

	@Test
	public void testAddItemManual() throws ParseException {
		Date different = DateUtils.parseDate("01/04/2005");
		
		// base case
		this.setBarcode("11");
		this._shouldCallGiveBarcodeFocus = true;
		
		_controller.addItem();
		testResetedAfterAdd();
		assertEquals(4, ItemFacade.instance().getNumItemsInProductAndContainer(
				ProductFacade.instance().getProduct("11"), 
				_target.getContainer()));
		
		// Enter new product barcode and cancel
		this.setBarcode("33");
		this.setEntryDate(different);
		this.setCount("4");
		this._shouldAddNewProduct = false;
		this._shouldCallShowProductView = true;
		this._shouldCallGiveBarcodeFocus = true;
		_controller.addItem();
		
		// everything should be reset (as is in demo)
		testResetedAfterAdd();
		assertTrue(this._didCallShowProductView);
		this._didCallShowProductView = false;
		
		// enter new product barcode and continue
		this.setBarcode("33");
		this.setEntryDate(different);
		this.setCount("4");
		this._shouldAddNewProduct = true;
		this._shouldCallShowProductView = true;
		this._shouldCallGiveBarcodeFocus = true;
		_controller.addItem();
		
		// everything should be reset (as is in demo)
		testResetedAfterAdd();
		assertTrue(this._didCallShowProductView);
		this._didCallShowProductView = false;		
		assertEquals(4, ItemFacade.instance().getNumItemsInProductAndContainer(
				ProductFacade.instance().getProduct("33"), 
				_target.getContainer()));	
		
		// Test products end up in child product group
		this.setBarcode("22");
		this._shouldCallGiveBarcodeFocus = true;
		_controller.addItem();
		assertEquals(1, ItemFacade.instance().getNumItemsInProductAndContainer(
				ProductFacade.instance().getProduct("22"), 
				_target.getContainer().lookupContainer("ProductGroup1")));
		
		
	}
	
	private void testResetedAfterAdd(){
		assertTrue("After add item, barcode field should be cleared", _currentBarcode.isEmpty());
		assertEquals("After add item, count should be reset to 1", "1",_currentCount);
		assertEquals("After add item, entry date should be reset to current date",
				DateUtils.currentDate(), _currentEntryDate);	
		assertFalse("Barcode is empty now, so this should be false",_addItemEnabled);
	}
	

	@Test
	public void testDone() {
		this._shouldCallClose = true;
		_controller.done();
		assert(this._didCallClose);
		
		// note: this method should not print barcodes since no 
		// items were added.
	}

	@Override
	public void displayInformationMessage(String message) {
		fail("This method isn't used");
		
	}

	@Override
	public void displayWarningMessage(String message) {
		fail("This message isn't used");
		
	}

	@Override
	public void displayErrorMessage(String message) {
		if (_expectedErrorMessage == null)
			fail("Wasn't expecting an error " + message);
		else
			assertEquals(_expectedErrorMessage,message);	
	}

	@Override
	public void setProducts(ProductData[] products) {
		_currentProducts = products;
	}

	@Override
	public ProductData getSelectedProduct() {
		return _selectedProduct;
	}

	@Override
	public void selectProduct(ProductData product) {
		if (product != null)
			assertTrue(Arrays.asList(_currentProducts).contains(product));
		_selectedProduct = product;
		if (_notifyOnSelection && _controller != null)
			_controller.selectedProductChanged();
		
	}

	@Override
	public void setItems(ItemData[] items) {
		_currentItems = items;
	}

	@Override
	public void close() {
		assertTrue(_shouldCallClose);
		_didCallClose = true;
		_shouldCallClose = false;
		
	}

	@Override
	public Date getEntryDate() {
		return _currentEntryDate;
	}

	@Override
	public void setEntryDate(Date value) {
		_currentEntryDate = value;
		if (_controller != null)
			_controller.entryDateChanged();
		
	}

	@Override
	public String getCount() {
		return _currentCount;
	}

	@Override
	public void setCount(String value) {
		_currentCount = value;
		if (_controller != null)
			_controller.countChanged();
		
	}

	@Override
	public void setBarcode(String barcode) {
		_currentBarcode = barcode;
		if (_controller != null)
			_controller.barcodeChanged();		
	}

	@Override
	public String getBarcode() {
		return _currentBarcode;
	}

	@Override
	public void giveBarcodeFocus() {
		assertTrue(_shouldCallGiveBarcodeFocus);
		_didCallGiveBarcodeFocus = true;
		_shouldCallGiveBarcodeFocus = false;
		
	}

	@Override
	public void setUseScanner(boolean value) {
		_useBarcodeScanner = value;
		if (_controller != null)
			_controller.useScannerChanged();
		
	}

	@Override
	public boolean getUseScanner() {
		return _useBarcodeScanner;
	}

	@Override
	public void enableItemAction(boolean value) {
		_addItemEnabled = value;	
	}

	@Override
	public void enableUndo(boolean value) {
		// TODO Not currently tested (not really part of phase 2)
		
	}

	@Override
	public void enableRedo(boolean value) {
		// TODO Not currently tested (not really part of phase 2)
		
	}

	@Override
	public ItemData getSelectedItem() {
		return _selectedItem;
	}

	@Override
	public void selectItem(ItemData item) {
		if (item != null)
			assertTrue(Arrays.asList(_currentItems).contains(item));
		_selectedItem = item;
	}

	@Override
	public void displayAddProductView() {
		assertTrue(_shouldCallShowProductView);
		_didCallShowProductView = true;
		_shouldCallShowProductView = false;
		
		if(_shouldAddNewProduct){
			try {
				ProductFacade.instance().addProduct(new Product(_currentBarcode, "Auto-added", "1", 
						"count", 0, 0));
			} catch (IllegalArgumentException | ModelException e) {
				fail("This should not happen");
			}
		}
		
	}

}
